docs(skills): tag the 16 comment-carrying JSON fences jsonc - #7473
Conversation
Sixteen fenced blocks under `skills/objectui/**` are tagged `json` while their bodies carry `//` comments -- syntax JSON forbids and JSONC allows -- so a reader or a gate that trusts the tag and parses the block fails. `check-skill-examples.mjs` already implements both dialects (`JSON_FENCE_LANGUAGES`, `parseJsonFence`); its header records that the corpus had zero `jsonc` fences, so the tag was the only thing missing. Tag-only, by line number, after asserting each opener is exactly the six bytes of the `json` info string: 11 in guides/schema-expressions.md, 4 in rules/protocol.md, 1 in rules/styling.md. +1 byte per fence, +16 bytes total, zero line-count change, and the whole diff is 16 opener lines. Seven of the sixteen now parse under the repository's own JSONC dialect; the other nine stay unparseable for a reason a tag cannot fix (seven list several top-level documents in one fence, two carry `...` elisions), which is recorded on objectui#7462 rather than repaired here. Ref: objectui#7462 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
|
Maintainer ruling, 2026-09-03, given in chat to the director seat (session_01WXyGTWPbbreqXow7Z2pZCk) on decision batch 2, item 3 (this PR, option A: merge as-is, the seventeenth fence keeps Executing on that instruction from the maintainer's own account: merging directly (squash); if a repository rule requires the merge queue, auto-merge (squash) is armed instead. No approving review is submitted by any seat; the human merge is the review record for the governed paths Generated by Claude Code |
|
Director seat (session_01WXyGTWPbbreqXow7Z2pZCk): the merge-queue build for this PR (17:14Z, enqueued by os-zhuang) was rejected, and the failure is this PR's own, not a flake. Failing job:
Mechanism: that test's Ask for the author seat (skills), in this PR: either widen that test's matcher to accept both Card #7462 keeps Generated by Claude Code |
The fence retag breaks a test that reads these files at run timeFlagging from the PR that was queued behind this one — the same red will recur on the next enqueue, so it is worth having the diagnosis written down before then. What failed. Merge group Both failures are in Mechanism. Its extractor is literal about the fence tag: const fence = /```json\n([\s\S]*?)```/g;The
Suggested fix, inside this PR: widen the extractor rather than revert the tags. The retag looks correct to me on its merits — those blocks carry const fence = /```jsonc?\n([\s\S]*?)```/g;Two things worth checking in the same pass:
Queue state. That failing group dequeued this PR; #7523 was re-speculated on its own against Generated by Claude Code |
…hide the bind example
The guide extractor in `skill-guide-data-table-binding.test.tsx` matched a
```json opener literally, so this branch's retag of `rules/protocol.md`'s four
comment-carrying fences made the `"bind": "customerNames"` list example
invisible to every assertion in the file. Two of them then read that absence as
data:
- the counter-probe "still teaches `bind` in a JSON block" —
"AssertionError: expected false to be true";
- "its `list` example renders one entry per bound item" —
"AssertionError: the guide must carry a parseable list example bound with
`bind`: expected undefined to be truthy".
The pull-request-level run never saw it: that job trims the suite for a
docs-only diff, and the full run is the merge_group one — which is why this
branch was ejected from the queue twice on the same failure.
`parseBlock` already strips `//` comments before `JSON.parse`, so jsonc bodies
parse today; only the extractor was behind. Widening it to accept both tags is
the whole fix. No retag is reverted and the `bind` block does not go back to
`json`: those fences carry `//` comments, which is the falsehood this branch
exists to remove.
Counts re-derived at ec67429 with a faithful re-implementation of `jsonBlocks` +
`parseBlock` — fences / data-table / list / bind=customerNames / offenders,
narrow then widened:
guides/schema-expressions.md 8/1/1/1/0 -> 19/1/5/1/0
rules/protocol.md 6/0/0/0/0 -> 10/0/1/1/0
guides/data-integration.md 4/1/1/1/0 -> 4/1/1/1/0
The data-table count is unchanged at 1 / 0 / 1, so `expect(extra).toEqual([])`
and the `[taught]` node selection are untouched; the offenders list stays empty;
the two failing assertions get their block back. The newly visible protocol.md
node is the same shape the other two guides already carry, character for
character: {"type":"list","bind":"customerNames"}.
Reverse verification, one mutation, proven on disk before any verdict was read:
restoring the narrow json-only extractor put the file back to blob 421bca4 (the
parent's blob for that path) with a widened-regex count of 0 and a narrow-regex
count of 1, and made exactly those two assertions red and nothing else —
"Tests 2 failed | 14 passed (16)", shared-lock verdict "command-exit 1". The
restore ran under an EXIT/INT/TERM trap with an absolute path and is proven by
hash: back to 76fdb28. With the fix in place the same whole-file command gives
"Test Files 1 passed (1)" / "Tests 16 passed (16)", lock verdict
"command-exit 0".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Queue fix pushed — new head What was wrong. What changed. The extractor now reads Counts re-derived at this head (fences / data-table / list / bind=customerNames / offenders), narrow then widened: Verdicts (exit codes captured by redirect before any pipe, quoting each gate's own line):
This needs a human before it can re-enter the queue: the earlier approving review was pinned to One note for whoever reads the description: the PATCH that appended the "Queue fix (2026-09-04)" section came back with its trailing attribution footer stripped by the platform, while the pre-existing body above it is byte-identical to what was there before (verified by comparing the stored markdown before and after). The session link is still in the description prose, so it is not re-posted here in a loop. Generated by Claude Code |
|
In-seat review of the queue fix — ACCEPT (skills seat linger, session What the seat checked itself, not from the report:
One thing this instance does not fix, recorded and not filed by this seat (it lands on Generated by Claude Code |
`parseJsonFence`'s docblock said "There are zero `jsonc` fences in the corpus today" as the reason the dialect is recognised. That stopped being true at PR #7473 (16 comment-carrying fences retagged `jsonc`) and PR #7608 moved it further (41 `json` / 29 `jsonc` today, confirmed with `node scripts/check-skill-examples.mjs --measure`). The sentence read as "this branch has never run in anger" — the opposite of the truth, and a risk that a reader deletes or weakens the branch as dead code. Rewritten to a count-free statement, in the same convention the file's own header uses for its other counts ("Re-derive it with `--measure`"): the branch is exercised by fences already in the corpus, not merely reserved for a hypothetical one. No behaviour change. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
Fixes #7462
Sixteen fenced blocks under
skills/objectui/were tagged```jsonwhiletheir bodies carry
//comments — syntax JSON forbids and JSONC allows. Thisretags exactly those sixteen to
```jsonc. Tag-only: the whole diff issixteen opener lines.
What was counted, at the branch point 0246d11
skills/objectui/**/*.mdholds 56 fences taggedjson(and zero taggedjsonc).17 of them fail
JSON.parse— the card's figure, reproduced exactly:12 in
guides/schema-expressions.md, 4 inrules/protocol.md, 1 inrules/styling.md.gap is exactly one block. Counting by "carries a
//comment or a...elision"(the claim comment's wording) gives 16; counting by "fails
JSON.parse" gives17. The seventeenth is
guides/schema-expressions.md:206, which uses no JSONCfeature at all — it is two top-level JSON documents listed in one fence. It keeps
its
jsontag, becausejsoncwould be a different false claim rather than atruer one: the block's defect is that it is a listing, not that it is a dialect.
That is a judgement call, recorded here and on the card, and it is reversible in
one byte if the maintainer reads the card's headline number as the scope.
Sites retagged, per file
skills/objectui/guides/schema-expressions.mdskills/objectui/rules/protocol.mdskills/objectui/rules/styling.mdEach opener was asserted to be exactly the six bytes of the
jsoninfo stringbefore it was rewritten; the retag script refuses the file otherwise.
Arithmetic
skills/objectui/guides/schema-expressions.mdskills/objectui/rules/protocol.mdskills/objectui/rules/styling.md+1 byte per retagged fence, sixteen fences, sixteen bytes, zero line-count change.
git diff -U0filtered to changed lines yields exactly 16 removals of thejsonopener and 16 additions of the
jsoncopener, and nothing else.Token ratchet: none exists. No script in this repository budgets tokens, bytes
or lines over
skills/**. The two budget scripts,check-eager-closure-budget.mjsand
render-budget-comment.mjs, measure the shipped JavaScript closure, notmarkdown. The five scripts that read
skills/at all arecheck-skill-examples.mjs,check-skills-paths.mjs,check-shell-escape-residue.mjs,check-governed-queue-guard.mjsandcheck-doc-snippet-types.mjs(the last only toNAME the tree as unscanned). So the arithmetic above is reported because the card
asked for it, not because anything gates on it.
What the tag buys, measured with the repository's own parser
check-skill-examples.mjsalready implements both dialects —JSON_FENCE_LANGUAGESis the set
json,jsonc, andparseJsonFencestrips comments and trailing commasfor
jsonconly. Its header records the corpus state this pull request changes:"There are zero
jsoncfences in the corpus today; the language is recognised sothat adding one is not a silent no-op."
Running that exact function over the corpus, before and after:
json, 0jsoncjson, 16jsoncjson, 7 of 16jsonc= 46 of 56So seven blocks become genuinely checkable — eligible for the gate's opt-in marker —
and nine do not. That residue is honest and is not repaired here, because
repairing it needs content edits and this flight is tag-only:
(
schema-expressions.md32, 170, 447, 463, 479, 492;protocol.md177);...elisions, which no JSON dialect accepts(
protocol.md157, 166);schema-expressions.md:206, which staysjson, same listing shape.Eleven blocks in total still parse under no dialect. They are recorded on the card
as a follow-up, not smuggled in here.
Decision:
check-doc-fence-languages.mjsdoes NOT gain askills/rootThe card asked whether this gate should be widened the way
check-skills-paths.mjswas by #7358. No — and the answer is measured rather than argued. Both legs
below ran against a throwaway mutation that added a
skillswalk tolistDocuments, proven on disk before any verdict was read (marker count 1, blobhash de67f1d to 194926b) and restored byte-identically afterwards (hash back to
de67f1d,
git diff HEADfor that path empty).Leg A — the gate itself reds, in the one mode it can never absorb. Widened, it
exits 1 on
skills/objectui/guides/project-setup.md:115: a```javascriptfence whose first line is
export default, which its quoted triage classifier callscode. Because
javascriptis not inUNHIGHLIGHTED_SPELLINGS, that is UNKNOWN mode,and the gate's header is explicit that UNKNOWN "⛔ can never be baselined and fails
on sight". So this is worse than the "would the baseline grow" test the card set:
the baseline could not take it at any size. Zero SYNONYM findings, one UNKNOWN.
Leg B — the widening is refused by the tree's own pin. The gate's scan surface is
pinned equal, element by element, to
check-doc-snippet-types's byscripts/__tests__/check-doc-fence-languages.test.ts("walks exactly the documentsthe snippet gate walks"). Under the mutation the two lists are 243 and 227 documents
and the assertion is false, the difference being exactly the 16
skills/objectuipages. Widening this gate alone is therefore not a small edit; it is a three-gate
surface move.
And the surface owner has already been named.
check-doc-snippet-types.mjsstates, beside
UNGATED_DOCS: "⛔skills/objectui/**is NOT claimed by any gatehere, and this line is the opposite of a claim on it: it is a governed, published
surface with its own review path, so pointing a doc gate at it is a decision for
whoever owns that surface — never a side effect of a root move." Doing it inside a
tag flight would be exactly the side effect that sentence forbids. For scale: that
gate is covered-by-default and compiles every
ts/tsxfence it walks, andskills/holds 112 of them.The four axes
find in
skills/is ONE block, and it is a genuine JavaScript config example, nothidden TypeScript. There is no accumulation to catch here today. The need the card
actually names — a tag that lies to a reader — is served in full by the retag,
which the gate would not have caught either: it judges TypeScript bodies under
non-TypeScript fences and says nothing whatsoever about
jsonversusjsonc.shape is one surface decision made once, by the surface's owner, moving all three
doc gates together with the
UNGATED_DOCSaccounting that decision implies. Thepatch-shaped alternative — widen one gate, then either re-fence a JavaScript block
as TypeScript or add
javascriptto a set of spellings for unhighlighted blocks —buys one gate's reach by corrupting two vocabularies that other gates read. Both
remedies are worse than the debt, and the debt is already written down by name.
not decide the widening half. An agent reading
skills/objectui/copies what thefence claims: a block tagged
jsoncarrying//teaches that comments are legalin the
.jsonfiles it is about to write, and metadata files are exactly wherethat error would land silently. The fence gate would not have caught that in any
configuration. Making the tag honest is the enforceable half available today;
widening a TypeScript-body gate is not related to it.
into a three-gate surface move plus a compile-coverage decision over a published,
governed tree is the scope creep the focus principle exists to refuse. The debt
stays named and unclaimed, which is the state that header calls strictly better
than an unnamed one.
Consequently there is no self-test leg to add (the card's conditional step): no
gate changed, so no gate needs a new fixture. The reverse verification that WOULD
have accompanied a widening was still run, as the two ablation legs above — they are
what makes "do not widen" a measurement instead of a preference.
The tag is load-bearing, not cosmetic — proven both directions
A tag-only change invites the question "does anything actually read this?", so it
was answered against the live gate rather than asserted. Throwaway mutation on
rules/styling.md, under a trap, both legs proven on disk before any verdict wasread:
jsonc. Marker lines 3 to 4, blob06654f3 to 60b888c.
check-skill-examplesexits 0:"JSON phase: 40 fence(s) parsed, 0 failed."
json. jsonc openers 1 to 0,blob 60b888c to 057fa51. The gate exits 1 and names the site:
"[json] skills/objectui/rules/styling.md:171 Expected double-quoted property
name in JSON at position 50 (line 3 column 31)."
git checkout HEADon the path, blob back to06654f3 — byte-identical to the HEAD blob —
git diff HEADfor that path empty,marker count back to 3, jsonc openers back to 1.
So the retag moves seven blocks from "cannot be opted in without the gate going red"
to "can". Opting them in is a content edit and is NOT done here; it is the natural
follow-up and is recorded on the card.
An earlier attempt at this probe used a
perlsubstitution whose anchor matchednothing. It exited 0 with the file untouched — the classic green no-op — and was
caught only because the mutation is proven by hash and grep count before any verdict
is read. Recording it because the guard is the reason the numbers above mean
anything.
Gates
Every exit code was captured by redirect before any pipe, and each row quotes the
gate's own verdict line.
node scripts/check-skill-examples.mjsnode scripts/check-doc-fence-languages.mjsskills/, which is the subject of the decision above.node scripts/check-skills-paths.mjsnode scripts/check-shell-escape-residue.mjsnode scripts/check-control-bytes.mjsnode scripts/check-doc-links.mjsnode scripts/check-changeset-presence.mjsnode scripts/check-governed-queue-guard.mjs --test(the three final paths)pnpm exec vitest runover 8scripts/__tests__specs, under the shared lockturbo run buildover the gate's own--build-filterclosure: "Tasks: 29 successful, 29 total". Lock verdict: "command-exit 0 · held the lock 294s".eslintover changed.mjsscripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectuipackage.jsonand.github/workflows/, which carry nopathsfilter for these gates.All ten runnable rows were re-run at the final commit
ec67429with a clean workingtree, and every exit code was captured by redirect before any pipe.
Not measured, and why
Governed Surface Queue Guard cannot run on a draft at all.
eslintover changed.mjsfiles. Nothing to run: the diff contains threemarkdown files and no script.
jsoncfences and onejsonfence that still parse under no dialect.Their content is untouched by design; they are a follow-up on the card.
skills/. Deliberately notmeasured. Compiling 112 fences against built
distto cost a surface move nobodyhas authorised is the scope this pull request declines.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LraLgQVGq8egUwfYZpbYt1
Generated by Claude Code
Queue fix (2026-09-04)
New head
dc4811c. One commit on top ofec67429; nothing above this sectionis changed, and no retag is reverted.
Why the queue rejected this pull request twice
packages/components/src/__tests__/skill-guide-data-table-binding.test.tsxreads thereal guides at run time, and its extractor was literal about the fence tag:
rules/protocol.md's"bind": "customerNames"list example is one of the fourfences this branch retags, so a
jsoncopener made it invisible to every assertionin that file. Two of them read the absence as data:
bindin a JSON block" — "expected false to be true";listexample renders one entry per bound item" — "the guide must carry aparseable list example bound with
bind: expected undefined to be truthy".The pull-request-level run never saw it: the "Decide whether this change needs a full
run" step trims the suite for a docs-only diff, and the full run is the
merge_groupone. Same failure both ejections.
The fix
Widen the extractor to read both tags — the opener becomes
jsonc?in thesame expression — and say so in the helper's doc comment.
parseBlockalready strips//comments beforeJSON.parse, so jsonc bodies parse today; only the extractor was behind. Thebindblock keeps its
jsonctag: those fences carry comments, which is the falsehood thisbranch exists to remove.
Files:
packages/components/src/__tests__/skill-guide-data-table-binding.test.tsxand.changeset/jsonc-fence-extractor-test-only.md(empty frontmatter — a test-only changedeclared as releasing nothing; the presence gate demanded a declaration once a
packages/components/srcfile entered the diff, and this is its explicit pass form).Counts, re-derived at this head
A faithful re-implementation of
jsonBlocks+parseBlock, run over the three guidesthe test names, narrow tag then widened — fences / data-table / list /
bind=customerNames / offenders:
jsononly)jsonc?)guides/schema-expressions.mdrules/protocol.mdguides/data-integration.mdThe
data-tablecount is unchanged at 1 / 0 / 1, soexpect(extra).toEqual([])andthe
[taught]node selection are untouched; the offenders list stays empty; the twofailing assertions get their block back. The newly visible
protocol.mdnode is theshape the other two guides already carry, character for character:
{"type":"list","bind":"customerNames"}— which is why widening it renders ratherthan merely parses.
guides/data-integration.mdis untouched by this branch and staysuntouched; the widening only makes a future retag there safe.
Reverse verification — one mutation, proven on disk before any verdict was read
Restore the narrow
json-only extractor on the committed state, under anEXIT/INT/TERM trap with an absolute path:
421bca4— byte-identical tothe parent's blob for that path — widened-regex count 0, narrow-regex count
1, all three read before the test ran.
passed (16)", "Test Files 1 failed (1)", with the two messages quoted above. Lock
verdict: "command-exit 1".
76fdb28,git diff HEADfor that path empty,working tree clean at the final commit.
With the fix, the same whole-file command: "Test Files 1 passed (1)" / "Tests 16
passed (16)", lock verdict "command-exit 0".
Gates, re-run at
dc4811cwith a clean working treeEvery exit code captured by redirect before any pipe; each row quotes the gate's own
verdict line.
pnpm exec vitest run packages/components/src/__tests__/skill-guide-data-table-binding.test.tsx(whole file, shared lock)node scripts/check-skill-examples.mjs(afterturbo run buildover its own--build-filter)node scripts/check-changeset-presence.mjsnode scripts/check-changeset-fixed.mjsnode scripts/check-changeset-no-major.mjsmajorbump."node scripts/check-changeset-overwrite.mjsnode scripts/check-control-bytes.mjsnode scripts/check-doc-fence-languages.mjsnode scripts/check-skills-paths.mjsnode scripts/check-shell-escape-residue.mjsnode scripts/check-package-self-import.mjsnode scripts/check-vi-mock-specifiers.mjsnode scripts/check-vi-mock-inherit.mjsnode scripts/check-governed-queue-guard.mjs --self-testnode scripts/check-governed-queue-guard.mjs --test(the five final paths)pnpm --filter @object-ui/components run type-checktsc -p tsconfig.test.json --listFilesnames the edited file exactly once, so this is a reading about it.pnpm exec eslint .inpackages/components(the whole changed package).tsxalone: exit 0, no output.Not measured, and why
merge_grouprun. Reported at push time by dispatchcontract. The whole-file run above is the local stand-in for the queue's full run.
tsc -p tsconfig.scripts.json. Does not apply: the changed file belongs to@object-ui/components, whose owntype-checkchainstsconfig.test.json, and thatis the project that reads it.
pnpm lint. Narrowed to the one changed PACKAGE, and thenarrowing is measured rather than asserted. Population read from eslint's own config:
the root flat config extends
tseslint.configs.recommended, not the type-checkedvariant, and its
languageOptionsnames noparserOptions.projectorprojectService— so no file's verdict depends on a TypeScript program, and a diffinside
packages/componentscannot move the verdict of a file in another package.File count read from
--format json:eslint .inpackages/componentsjudged448 files, 0 errors (934 pre-existing warnings), exit 0, and the edited file is in
that judged set.
jsoncfences and onejsonfence that still parse under no dialect.Unchanged by this section, still the follow-up recorded on the card.
What this needs from a human
The earlier approving review was pinned to
ec67429. The new headdc4811cneeds afresh approval pinned to it before the merge queue will take this pull request —
that is the approver's action. Nothing here touched the draft/ready state, reviewers,
labels, auto-merge, or queue membership; the push landed only because the failing
group had already dequeued this pull request on its own.